#!/usr/bin/python
'''
bootstrap.py
by: Brett and the Blender Research Lab. PH
License: BSD

1. takes blender's source code, copies, and hacks it, saving it to "blender_source"
2. runs cmake and builds libblender.so
3. generates ctypes wrappers to libblender.so

Example:
	./bootstrap.py /path/to/blender --headless

Installing:
	get the source tarball from http://blender.org
	get Rpythonic from http://code.google.com/p/rpythonic

Fedora Notes:	(should work on a fresh Fedora)
	sudo yum install
		gcc
		gcc-c++
		cmake
		libjpeg-devel
		openjpeg-devel
		libpng-devel
		freetype-devel
		openexr-devel
		libXi-devel
		glew-devel
		boost-devel

		mingw32-\*

'''

import os, sys, shutil, ctypes

IS32BIT = (ctypes.sizeof(ctypes.c_void_p)==4)

if len(sys.argv)>1 and not sys.argv[1].startswith('--'): BLENDER_DIR = sys.argv[1]
else: BLENDER_DIR = os.path.expanduser('~/blender-2.65a')

BLENDER_DIR = os.path.abspath( BLENDER_DIR )
print('blender source directory: %s' %BLENDER_DIR )
assert os.path.isdir( BLENDER_DIR )


PATHS = [
	'build_files/cmake',
	'source/tests',	# required by source/CMakeLists.txt:36

	'source/blender/editors',
	'source/blender/windowmanager',
	'source/blender/blenkernel',
	'source/blender/blenlib',
	'source/blender/bmesh',
	'source/blender/render',
	'source/blender/blenfont',
	'source/blender/blenloader',
	#'source/blender/blenpluginapi',  ## deprecated in blender2.65
	'source/blender/ikplugin',
	'source/blender/gpu',
	'source/blender/imbuf',
	'source/blender/avi',
	'source/blender/nodes',
	'source/blender/modifiers',

	'source/blender/python',	# BPY_extern.h required by: "source/blender/editors/interface/interface.c" even if WITH_PYTHON is set to OFF!
	'source/blender/collada',

	'source/blender/makesdna',
	'source/blender/makesrna',
	## new in blender2.65
	'source/blender/datatoc',
	'source/blender/opencl',
	'source/blender/compositor',
	'intern/opencolorio',
	'intern/raskter',
	'release/datafiles',
	'intern/locale',
	## end new in blender2.65

	'source/creator',
	
	'source/icons',	# required by MS Windows

	'intern/string',
	'intern/ghost',
	'intern/guardedalloc',
	'intern/moto',
	'intern/memutil',
	'intern/iksolver',
	'intern/opennl',
	'intern/mikktspace',
	'intern/utfconv',	# for MS Windows
	'intern/ffmpeg',	# where is this included from?

	'extern/colamd',
	'extern/binreloc',	# Linux only
	'extern/glew',
	'extern/libopenjpeg',	# for MS Windows
	'extern/lzo',
	'extern/lzma',
	'extern/xdnd',	# Linux only

]

if '--audaspace' in sys.argv:
	PATHS.append( 'intern/audaspace' )
if '--remesh' in sys.argv:
	PATHS.append( 'intern/dualcon' )
if '--fluid' in sys.argv or True:
	# LBM_fluidsim.h required by: source/blender/editors/physics/physics_fluid.c
	PATHS.append( 'intern/elbeem' )
if '--smoke' in sys.argv or True:
	# smoke_API.h required by: source/blender/editors/space_view3d/drawobject.c
	PATHS.append( 'intern/smoke' )
if '--decimate' in sys.argv:
	PATHS.append( 'intern/container' )
	PATHS.append( 'intern/decimation' )
if '--boolean' in sys.argv:
	PATHS.append( 'intern/boolop' )
	PATHS.append( 'intern/bsp' )	# required by: blenkernel/BKE_booleanops_mesh.h
if '--itasc' in sys.argv:
	PATHS.append( 'intern/itasc' )
if '--cycles' in sys.argv or True:
	# CCL_api.h required by: source/blender/makesrna/intern/rna_userdef.c
	PATHS.append( 'intern/cycles' )

if '--libmv' in sys.argv:
	PATHS.append( 'extern/libmv' )
if '--carve' in sys.argv:
	PATHS.append( 'extern/carve' )





###########################################
def ignore_callback( path, names ):
	if path in 'test'.split(): return list(names)
	ignore = 'GNUmakefile SConscript'.split()
	r = []
	for name in names:
		if name in ignore: r.append( name )
	return r

def copy_blender_source():

	for path in PATHS:
		src = os.path.join( BLENDER_DIR, path )
		dst = os.path.join('blender_source',path)
		if os.path.isdir(dst): continue
		print( dst )
		shutil.copytree( src, dst, ignore=ignore_callback)


	shutil.copyfile(
		os.path.join(BLENDER_DIR,'COPYING'),
		'blender_source/COPYING'
	)

	shutil.copyfile(
		os.path.join(BLENDER_DIR,'source/CMakeLists.txt'),
		'blender_source/source/CMakeLists.txt'
	)

	shutil.copyfile(
		os.path.join(BLENDER_DIR,'source/blender/CMakeLists.txt'),
		'blender_source/source/blender/CMakeLists.txt'
	)

	shutil.copyfile(
		os.path.join(BLENDER_DIR,'intern/CMakeLists.txt'),
		'blender_source/intern/CMakeLists.txt'
	)
	shutil.copyfile(
		os.path.join(BLENDER_DIR,'extern/CMakeLists.txt'),
		'blender_source/extern/CMakeLists.txt'
	)


def hack_cmake_files():
	##############################################################
	############### hack the make CMakeLists.txt file ################
	data = open( os.path.join(BLENDER_DIR,'CMakeLists.txt'), 'rb' ).read()
	hacked = []
	for line in data.splitlines():
		a = line.strip()
		if a.startswith('option'):
			## ensure valid user settings ##
			if a.startswith( 'option(WITH_BLENDER' ): assert 'ON' in line
			elif a.startswith( 'option(WITH_PYTHON_MODULE' ): assert 'OFF' in line
			elif a.startswith( 'option(WITH_PLAYER' ): assert 'OFF' in line

			## force custom settings ##
			if a.startswith( 'option(WITH_PYTHON' ):
				line = 'option(WITH_PYTHON OFF)'
			elif a.startswith( 'option(WITH_IK_ITASC' ):
				if '--itasc' in sys.argv:
					line = 'option(WITH_IK_ITASC ON)'
				else:
					line = 'option(WITH_IK_ITASC OFF)'

			elif a.startswith( 'option(WITH_BULLET' ):
				line = 'option(WITH_BULLET OFF)'

			elif a.startswith( 'option(WITH_GAMEENGINE' ):
				line = 'option(WITH_GAMEENGINE OFF)'

			elif a.startswith( 'option(WITH_AUDASPACE' ):
				if '--audaspace' in sys.argv:
					line = 'option(WITH_AUDASPACE ON)'
				else:
					line = 'option(WITH_AUDASPACE OFF)'


			elif a.startswith( 'option(WITH_SDL' ):
				line = 'option(WITH_SDL OFF)'

			elif a.startswith( 'option(WITH_OPENMP' ):
				line = 'option(WITH_OPENMP OFF)'

			############## MODIFIERS ################
			elif a.startswith( 'option(WITH_MOD_FLUID' ):
				if '--fluid' in sys.argv:
					line = 'option(WITH_MOD_FLUID ON)'
				else:
					line = 'option(WITH_MOD_FLUID OFF)'

			elif a.startswith( 'option(WITH_MOD_SMOKE' ):
				if '--smoke' in sys.argv:
					line = 'option(WITH_MOD_SMOKE ON)'
				else:
					line = 'option(WITH_MOD_SMOKE OFF)'

			elif a.startswith( 'option(WITH_MOD_DECIMATE' ):
				if '--decimate' in sys.argv:
					line = 'option(WITH_MOD_DECIMATE ON)'
				else:
					line = 'option(WITH_MOD_DECIMATE OFF)'

			elif a.startswith( 'option(WITH_MOD_BOOLEAN' ):
				if '--boolean' in sys.argv:
					line = 'option(WITH_MOD_BOOLEAN ON)'
				else:
					line = 'option(WITH_MOD_BOOLEAN OFF)'

			elif a.startswith( 'option(WITH_MOD_REMESH' ):
				if '--remesh' in sys.argv:
					line = 'option(WITH_MOD_REMESH ON)'
				else:
					line = 'option(WITH_MOD_REMESH OFF)'


			##########################################
			elif a.startswith( 'option(WITH_OPENAL' ):
				if '--openal' in sys.argv:
					line = 'option(WITH_OPENAL ON)'
				else:
					line = 'option(WITH_OPENAL OFF)'

			elif a.startswith( 'option(WITH_LIBMV' ):
				if '--libmv' in sys.argv:
					line = 'option(WITH_LIBMV ON)'
				else:
					line = 'option(WITH_LIBMV OFF)'

			elif a.startswith( 'option(WITH_CARVE' ):	# requires boost!?
				if '--carve' in sys.argv:
					line = 'option(WITH_CARVE ON)'
				else:
					line = 'option(WITH_CARVE OFF)'


			elif a.startswith( 'option(WITH_PYTHON_INSTALL' ):
				line = 'option(WITH_PYTHON_INSTALL OFF)'

			elif a.startswith( 'option(WITH_CYCLES' ):
				if '--cycles' in sys.argv:
					line = 'option(WITH_CYCLES ON)'
				else:
					line = 'option(WITH_CYCLES OFF)'


			elif a.startswith( 'option(WITH_HEADLESS' ):
				if '--headless' in sys.argv:
					line = 'option(WITH_HEADLESS ON)'
				else:
					line = 'option(WITH_HEADLESS OFF)'


			elif a.startswith( 'option(WITH_GHOST_SDL' ):
				if '--ghost-sdl' in sys.argv:
					line = 'option(WITH_GHOST_SDL ON)'
				else:
					line = 'option(WITH_GHOST_SDL OFF)'

			elif a.startswith( 'option(WITH_IMAGE' ):
				if 'WITH_IMAGE_OPENJPEG' in a: pass
				else: line = a.split()[0] + ' OFF)'

			elif a.startswith( 'option(WITH_INTERNATIONAL' ):
				line = 'option(WITH_INTERNATIONAL OFF)'



		hacked.append( line )

	f = open( os.path.join('blender_source','CMakeLists.txt'), 'wb' )
	f.write( '\n'.join( hacked ) )
	f.close()

	## hack to make libblender.so ##
	data = open( os.path.join(BLENDER_DIR,'source/creator/CMakeLists.txt'), 'rb' ).read()
	hacked = []
	for line in data.splitlines():
		# note that 'SHARED' works on Linux and Windows,
		# but not OSX which _must_ be 'MODULE'
		#add_library(blender MODULE ${SRC})
		if line.strip().startswith( 'add_executable(blender' ):
			line = '	add_library(blender MODULE ${SRC})'
		hacked.append( line )

	f = open( os.path.join('blender_source','source/creator/CMakeLists.txt'), 'wb' )
	f.write( '\n'.join( hacked ) )
	f.close()

def do_cmake_build():
	if not os.path.isdir('build'): os.mkdir('build')
	os.system('cd build; cmake -g "Unix Makefiles" ../blender_source/.; make')
	if sys.platform == 'linux2':
		if IS32BIT: path = 'libblender/linux32'
		else: path = 'libblender/linux64'
		if not os.path.isdir( path ): os.makedirs(path)
		shutil.copyfile( 'build/lib/libblender.so', os.path.join(path,'libblender.so') )

	## not using the Data API ##
	#shutil.copyfile( 'build/source/blender/makesrna/intern/RNA_blender.h', 'RNA_blender.h' )


def generate_wrappers():
	sys.path.append('../rpythonic')
	import rpythonic
	rpythonic.set_cache('.')

	includes = []
	dirs = '''
intern/ghost 
source/blender/ikplugin 
source/blender/blenloader 
source/blender/gpu 
source/blender/windowmanager 
source/blender/editors/include 
source/blender/render/extern/include 
source/blender/imbuf 
source/blender/makesrna 
source/blender/makesdna 
source/blender/blenkernel 
source/blender/blenlib 
source/blender/blenfont 
source/blender/python 
source/blender/bmesh 
intern/guardedalloc 
source/blender/collada
'''.split()

	for d in dirs: includes.append( os.path.join( 'blender_source', d ) )

	rpythonic.wrap( 'libblender', 
		header='libblender.h',
		defines=['__LITTLE_ENDIAN__', 'DNA_DEPRECATED_ALLOW'],
		includes=includes,
		library = 'libblender.so',
	)


##################################################################
##################################################################
if os.path.isdir( BLENDER_DIR ):
	copy_blender_source()
	hack_cmake_files()
else:
	print('not doing copy and hack of blender source')
	print('try: ./bootstrap.py /path/to/blender-source-code')

do_cmake_build()	# TODO cross compile for MS Windows


if sys.platform == 'linux2' and '--ctypes' in sys.argv:	# Rpythonic wrapper generation only tested on Linux
	generate_wrappers()
	## clean up PyCparser cache ##
	os.unlink('parser.out')
	os.unlink('yacctab.py')

